package com.yllt4cloud.ddauth.util.interceptor;

import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;


/**
 * @author HuangRiXuan 2018-07-17
 */
@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {
        MappedStatement.class, Object.class}),
    @Signature(type = Executor.class, method = "query", args = {
        MappedStatement.class, Object.class, RowBounds.class,
        ResultHandler.class})})
public class MybatisLogInterceptor implements Interceptor {

    private static final Pattern pattern1 = Pattern.compile("\\?(?=\\s*[^']*\\s*,?\\s*(\\w|$))");

    private static final Pattern pattern2 = Pattern.compile("[\\s]+");

    /**
     * 是否开启SQL调试窗口
     **/
    private static Boolean debugEnabled = Boolean.FALSE;

    private static Boolean outputToWindow = Boolean.FALSE;

    // 排除以下定时SQL的输出
    private static final List<String> excludes = Arrays.asList(
        "com.yllt.mapper.zike.ReserveListMapper.listReserveList",
        "com.yllt.mapper.vip.VipJiFenJingYanZhiZengSongMapper.queryZiKeVipJiFenJingYanZhiResultList",
        "com.yllt4cloud.ddauth。mapper.DataDictMapper.selectByFlag",
        "com.yllt.mapper.shouyin.DuiWaiZhiFuJiLuMapper.getDuiWaiZhiFuJiLuList",
        "com.yllt.mapper.shouyin.DuiWaiZhiFuJiLuMapper.queryIpadZhiFuJiLuLeftJoinXiXian",
        "com.yllt.mapper.shouyin.DuiWaiZhiFuJiLuMapper.queryIpadZhiFuJiLu",
        "com.yllt.mapper.jiushui.ZaiXianJiCunJiuMapper.query",
        "com.yllt.mapper.shouyin.DuiWaiZhiFuJiLuMapper.query",
        "com.yllt.yezhi.chupin.mapper.ChuPinDanHaoZhanDanXiXianMapper.queryChuPinPrintXiXianDetail",
        "com.yllt.yezhi.chupin.mapper.ChuPinZhuangBinFangMapper.queryChuPinZhuangBinFang",
        "com.yllt.yezhi.zike.mapper.ReserveListMapper.listReserveList"
    );

    private static volatile MybatisLogForm mybatisLogForm;

    /* 检查当前应用是否正在Docker进程内执行，即检查init进程是否使用cgroups来切割LXC */
    public static boolean isRunningInsideDocker() {
        try (Stream<String> stream = Files.lines(Paths.get("/proc/1/cgroup"))) {
            return stream.anyMatch(line -> line.contains("/docker"));
        } catch (IOException e) {
            return false;
        }
    }

    /* 检查当前应用是否正在一个Headless的Runtime下运行 */
    public static boolean ifEnvIsFitToDbgWindowToBeRun() {
        return !isRunningInsideDocker() && !GraphicsEnvironment.isHeadless();
        // return true;
    }


    static {
        ResourceBundle resourceBundle = ResourceBundle.getBundle("debugger", Locale.SIMPLIFIED_CHINESE);
        if (resourceBundle.containsKey("enable"))
            debugEnabled = Boolean.valueOf(resourceBundle.getString("enable"));
        if (resourceBundle.containsKey("window"))
            outputToWindow = Boolean.valueOf(resourceBundle.getString("window"));
        if (debugEnabled && outputToWindow && ifEnvIsFitToDbgWindowToBeRun()) {
//            String lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
            String lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
            try {
                UIManager.setLookAndFeel(lookAndFeel);
            } catch (ClassNotFoundException | UnsupportedLookAndFeelException | IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
            }
            if (mybatisLogForm == null) {
                mybatisLogForm = new MybatisLogForm();
                /* Create and display the form */
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        mybatisLogForm.setVisible(true);
                    }
                });
            }
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (debugEnabled) {
            Logger log = LoggerFactory.getLogger(getClass());
            long uniqueCellId = 0;
            try {
                MappedStatement mappedStatement = (MappedStatement) invocation
                    .getArgs()[0];
                Object parameter = null;
                if (invocation.getArgs().length > 1) {
                    parameter = invocation.getArgs()[1];
                }
                String sqlId = mappedStatement.getId();
                BoundSql boundSql = mappedStatement.getBoundSql(parameter);
                Configuration configuration = mappedStatement.getConfiguration();
                String executableSql = getParameterizedSql(configuration, boundSql, sqlId);
                log = LoggerFactory.getLogger(sqlId);
                if (!excludes.contains(sqlId)) {
                    if (mybatisLogForm != null) {
                        if (executableSql.length() > 0) {
                            uniqueCellId = mybatisLogForm.addCell(mappedStatement.getSqlCommandType().name(), sqlId, executableSql);
                        }
                    } else {
                        System.out.println("\n" + "执行了SQL语句 : " + executableSql + "\n");
//                        log.info(executableSql);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
//                if (log.isErrorEnabled())
//                    log.error(e.getMessage(), e);
            }
            long currNano = System.nanoTime();
            Object result=null;
            if(invocation!=null){
                 result = invocation.proceed();
            }
            if (mybatisLogForm != null) {
                mybatisLogForm.addElapsedMillisToCell(uniqueCellId, new BigDecimal((double) (System.nanoTime() - currNano) / 1000000.d).setScale(2, RoundingMode.UP));
            }
            return result;
        } else {
            return invocation.proceed();
        }
    }

    private static String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(
                DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "null";
            }

        }
        return value;
    }

    private String getParameterizedSql(Configuration configuration, BoundSql boundSql, String sqlId) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql
            .getParameterMappings();
        String sql = pattern2.matcher(boundSql.getSql()).replaceAll(" ");
        if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = pattern1.matcher(sql).replaceFirst(Matcher.quoteReplacement(getParameterValue(parameterObject)));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = pattern1.matcher(sql).replaceFirst(Matcher.quoteReplacement(getParameterValue(obj)));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = pattern1.matcher(sql).replaceFirst(Matcher.quoteReplacement(getParameterValue(obj)));
                    } else {
                        sql = pattern1.matcher(sql).replaceFirst("缺失");
                    }
                }
            }
        }
        return sql;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties0) {
        Properties properties = properties0;
    }
}
